/*
 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.lang.ref;

/**
 * Reference queues, to which registered reference objects are appended by the
 * garbage collector after the appropriate reachability changes are detected.
 *
 * @author Mark Reinhold
 * @since 1.2
 */

public class ReferenceQueue<T> {

  /**
   * Constructs a new reference-object queue.
   */
  public ReferenceQueue() {
  }

  private static class Null<S> extends ReferenceQueue<S> {

    boolean enqueue(Reference<? extends S> r) {
      return false;
    }
  }

  static ReferenceQueue<Object> NULL = new Null<>();
  static ReferenceQueue<Object> ENQUEUED = new Null<>();

  static private class Lock {

  }

  ;
  private Lock lock = new Lock();
  private volatile Reference<? extends T> head = null;
  private long queueLength = 0;

  boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */
    synchronized (lock) {
      // Check that since getting the lock this reference hasn't already been
      // enqueued (and even then removed)
      ReferenceQueue<?> queue = r.queue;
      if ((queue == NULL) || (queue == ENQUEUED)) {
        return false;
      }
      assert queue == this;
      r.queue = ENQUEUED;
      r.next = (head == null) ? r : head;
      head = r;
      queueLength++;
      if (r instanceof FinalReference) {
        sun.misc.VM.addFinalRefCount(1);
      }
      lock.notifyAll();
      return true;
    }
  }

  @SuppressWarnings("unchecked")
  private Reference<? extends T> reallyPoll() {       /* Must hold lock */
    Reference<? extends T> r = head;
    if (r != null) {
      head = (r.next == r) ?
          null :
          r.next; // Unchecked due to the next field having a raw type in Reference
      r.queue = NULL;
      r.next = r;
      queueLength--;
      if (r instanceof FinalReference) {
        sun.misc.VM.addFinalRefCount(-1);
      }
      return r;
    }
    return null;
  }

  /**
   * Polls this queue to see if a reference object is available.  If one is
   * available without further delay then it is removed from the queue and
   * returned.  Otherwise this method immediately returns <tt>null</tt>.
   *
   * @return A reference object, if one was immediately available, otherwise <code>null</code>
   */
  public Reference<? extends T> poll() {
    if (head == null) {
      return null;
    }
    synchronized (lock) {
      return reallyPoll();
    }
  }

  /**
   * Removes the next reference object in this queue, blocking until either
   * one becomes available or the given timeout period expires.
   *
   * <p> This method does not offer real-time guarantees: It schedules the
   * timeout as if by invoking the {@link Object#wait(long)} method.
   *
   * @param timeout If positive, block for up to <code>timeout</code> milliseconds while waiting for
   * a reference to be added to this queue.  If zero, block indefinitely.
   * @return A reference object, if one was available within the specified timeout period, otherwise
   * <code>null</code>
   * @throws IllegalArgumentException If the value of the timeout argument is negative
   * @throws InterruptedException If the timeout wait is interrupted
   */
  public Reference<? extends T> remove(long timeout)
      throws IllegalArgumentException, InterruptedException {
    if (timeout < 0) {
      throw new IllegalArgumentException("Negative timeout value");
    }
    synchronized (lock) {
      Reference<? extends T> r = reallyPoll();
      if (r != null) {
        return r;
      }
      long start = (timeout == 0) ? 0 : System.nanoTime();
      for (; ; ) {
        lock.wait(timeout);
        r = reallyPoll();
        if (r != null) {
          return r;
        }
        if (timeout != 0) {
          long end = System.nanoTime();
          timeout -= (end - start) / 1000_000;
          if (timeout <= 0) {
            return null;
          }
          start = end;
        }
      }
    }
  }

  /**
   * Removes the next reference object in this queue, blocking until one
   * becomes available.
   *
   * @return A reference object, blocking until one becomes available
   * @throws InterruptedException If the wait is interrupted
   */
  public Reference<? extends T> remove() throws InterruptedException {
    return remove(0);
  }

}
